﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SpotItComponents;

using Vitruvian.Distribution;
using Vitruvian.Distribution.SyncPatterns;
using Vitruvian.Serialization;
using Vitruvian.Services;

using Vitruvian.Logging;
using Vitruvian.Testing;

// TODO: Something

namespace SpotItServerAPI
{
    [ContainsTests("API.GameManager")]
    [DistributionInfo]
    [OptimisticSerialization]
    public class GameManager : DistributedManager
    {
        #region Private Data Members
        protected CategoryManager categoryManager = null;
        protected DeckManager deckManager = null;
        private List<Game> games = new List<Game>();
        #endregion
        
        #region Constructors
        public GameManager() : base("Game Manager")
        {
            logger = Logger.GetLogger(typeof(GameManager));
        }
        #endregion

        #region Public Properties and Methods -- Clients can use these methods
        /// <summary>
        /// CreateGame Method:
        /// 
        /// This method creates a new using a playing deck of a specific game type for a maximum number of players.  The game
        /// have a descriptive title.
        /// </summary>
        /// <param name="currentSession">Current user's session</param>
        /// <param name="playingDeck">The playing deck that the game will use.  You can get a list of available playing decks
        /// from DeckManager.AvailablePlayingGame</param>
        /// <param name="gameType">The desired game type -- see GameType for possible values</param>
        /// <param name="title">The desired title</param>
        /// <param name="maxPlayers">The maximum number of players to accept in the game.</param>
        /// <param name="firstPlayersMonitor">The monitor for the first player (the one who call this method)</param>
        /// <returns></returns>
        [SyncPattern("RPC")]
        public virtual OperationResult CreateGame(Session currentSession, PlayingDeck playingDeck, GameType gameType, string title, int maxPlayers, GameMonitor firstPlayersMonitor)
        {
            OperationMethod mth = new OperationMethod(InternalCreateGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, playingDeck, gameType, title, maxPlayers, firstPlayersMonitor);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult AvailableGames(Session currentSession)
        {
            OperationMethod mth = new OperationMethod(InternalAvailableGames);
            return DoOperation(currentSession, mth, SecurityLevel.Player);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult JoinGame(Session currentSession, Guid gameId, GameMonitor monitor)
        {
            OperationMethod mth = new OperationMethod(InternalJoinGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId, monitor);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult StartGame(Session currentSession, Guid gameId)
        {
            OperationMethod mth = new OperationMethod(InternalStartGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult GamesInProgess(Session currentSession)
        {
            OperationMethod mth = new OperationMethod(InternalGamesInProgress);
            return DoOperation(currentSession, mth, SecurityLevel.Player);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult MatchWithCenterCard(Session currentSession, Guid gameId, Guid myCard, Guid myCardsHotSpot, Guid centerCardsHotSpot)
        {
            OperationMethod mth = new OperationMethod(InternaMatchWithCenterCard);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId, myCard, myCardsHotSpot, centerCardsHotSpot);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult MatchWithOtherPlayer(Session currentSession, Guid gameId, Guid myCard, Guid myCardsHotSpot, int otherPlayerCards, Guid otherPlayerCardsHotSpot)
        {
            OperationMethod mth = new OperationMethod(InternaMatchWithOtherPlayer);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId, myCard, myCardsHotSpot, otherPlayerCards, otherPlayerCardsHotSpot);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult PauseGame(Session currentSession, Guid gameId)
        {
            OperationMethod mth = new OperationMethod(InternalPauseGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult ResumeGame(Session currentSession, Guid gameId)
        {
            OperationMethod mth = new OperationMethod(InternalResumeGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult AbortGame(Session currentSession, Guid gameId)
        {
            OperationMethod mth = new OperationMethod(InternalAbortGame);
            return DoOperation(currentSession, mth, SecurityLevel.Player, gameId);
        }


        [SyncPattern("RPC")]
        public virtual OperationResult HighScores(Session currentSession, Guid playingDeckId)
        {
            OperationMethod mth = new OperationMethod(InternalHighScores);
            return DoOperation(currentSession, mth, SecurityLevel.Player, playingDeckId);
        }

        #endregion

        #region Internal Methods for DoOperation
        protected override OperationResult PreCondition()
        {
            OperationResult result = new OperationResult();
            sessionManager = ServiceRegistry.GetPreferredService<SessionManager>();
            categoryManager = ServiceRegistry.GetPreferredService<CategoryManager>();
            deckManager = ServiceRegistry.GetPreferredService<DeckManager>();
            if (categoryManager == null)
                result = new OperationResult("Cannot locate a Category Manager -- please restart server process");
            else if (deckManager == null)
                result = new OperationResult("Cannot locate a Deck  Manager -- please restart server process");
            else if (sessionManager == null)
                result = new OperationResult("Cannot locate a Session  Manager -- please restart server process");
            return result;
        }

        private OperationResult InternalCreateGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;
            PlayingDeck playingDeck = opParams[1] as PlayingDeck;
            GameType gameType = (GameType) opParams[2];
            string title = (string) opParams[3];
            int maxPlayers = (int) opParams[4];
            GameMonitor monitor = opParams[5] as GameMonitor;
            if (playingDeck == null)
                result.ErrorMessage = "No playing deck specified";
            else
            {
                result = deckManager.Lookup(currentSession, playingDeck.DeckId);
                if (result.Success)
                {
                    Deck realDeck = result.Data as Deck;
                    Game newGame = new Game(currentSession, playingDeck, realDeck, gameType, title, maxPlayers, monitor);
                    result.Data = newGame.GameInfo;
                    games.Add(newGame);
                }
            }
            return result;
        }

        private OperationResult InternalAvailableGames(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            List<GameInfo> gameInforList = new List<GameInfo>();
            foreach (Game g in games)
                gameInforList.Add(g.GameInfo);

            result.Data = gameInforList;

            return result;
        }


        private OperationResult InternalJoinGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            Guid gameId = (Guid) opParams[1];
            GameMonitor monitor = opParams[2] as GameMonitor;
            if (gameId == Guid.Empty)
                result.ErrorMessage = "No game specified";
            else if (monitor == null)
                result.ErrorMessage = "No monitor specified";
            else
            {
                Game game = FindGame(gameId);
                if (game == null)
                    result.ErrorMessage = "Cannot find the specified game";
                else
                    game.AddPlayer(currentSession, monitor);
            }
            
            return result;
        }

        private OperationResult InternalStartGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }


        private OperationResult InternalGamesInProgress(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }

        private OperationResult InternaMatchWithCenterCard(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }


        private OperationResult InternaMatchWithOtherPlayer(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }


        private OperationResult InternalPauseGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }

        private OperationResult InternalResumeGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }

        private OperationResult InternalAbortGame(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }

        private OperationResult InternalHighScores(object[] opParams)
        {
            OperationResult result = new OperationResult();
            Session currentSession = opParams[0] as Session;

            // TODO

            return result;
        }

        private Game FindGame(Guid gameId)
        {
            Game result = null;

            foreach (Game g in games)
            {
                if (g.GameInfo.Id == gameId)
                {
                    result = g;
                    break;
                }
            }

            return result;
        }

        #endregion

        #region Server-only public methods (not used by client)

        #endregion

        #region Unit Test Cases
#if (DEBUG)
        private Session testSession = null;
        private SymbolManager symbolManager = null;
        private GameManager gameManager = null;

        [OneTimeSetup]
        private void Test_GameManager_Setup()
        {
            sessionManager = ServiceRegistry.GetPreferredService<SessionManager>();
            if (sessionManager == null)
            {
                sessionManager = new SessionManager();
                sessionManager.Init();
                ServiceRegistry.Add(sessionManager);
            }

            categoryManager = ServiceRegistry.GetPreferredService<CategoryManager>();
            if (categoryManager == null)
            {
                categoryManager = new CategoryManager();
                categoryManager.Init();
                ServiceRegistry.Add(categoryManager);
            }

            symbolManager = ServiceRegistry.GetPreferredService<SymbolManager>();
            if (symbolManager == null)
            {
                symbolManager = new SymbolManager();
                symbolManager.SymbolSetsDirectory = "SymbolSets";
                symbolManager.Init();
                ServiceRegistry.Add(symbolManager);
            }

            deckManager = ServiceRegistry.GetPreferredService<DeckManager>();
            if (deckManager == null)
            {
                deckManager = new DeckManager();
                deckManager.DecksDirectory = "Decks";
                deckManager.Init();
                ServiceRegistry.Add(deckManager);
            }

            gameManager = ServiceRegistry.GetPreferredService<GameManager>();
            if (gameManager == null)
            {
                gameManager = new GameManager();
                gameManager.Init();
                ServiceRegistry.Add(gameManager);
            }

            UserAccount tmpAccount = new UserAccount("tmp", "tmp", "Temporary", "");
            tmpAccount.IsAdmin = true;
            tmpAccount.IsDesigner = true;
            testSession = sessionManager.CreateSession(tmpAccount);
        }

        [Test(1)]
        public void Test_GameManager_BasicGameScenario()
        {
            deckManager.Test_SetupSampleDecks(testSession);

            // Get a deck to play with
            OperationResult result = deckManager.AvailablePlayingDecks(testSession);
            Assert.IsTrue(result.Success);
            List<PlayingDeck> deckList = result.Data as List<PlayingDeck>;
            Assert.IsNotNull(deckList);
            Assert.IsTrue(0<deckList.Count);
            PlayingDeck deck1 = deckList[0];

            // Create a GameMonitor the first player and wire up all the event handlers
            GameMonitor gameMonitor1 = new GameMonitor();
            gameMonitor1.GameInitialized += new GameEventHandler(gameMonitor1_GameInitialized);
            gameMonitor1.PlayerJoined += new GameEventHandler(gameMonitor1_PlayerJoined);
            gameMonitor1.GameStarted += new GameEventHandler(gameMonitor1_GameStarted);
            gameMonitor1.MyCardsChanged += new GameEventHandler(gameMonitor1_MyCardsChanged);
            gameMonitor1.OtherPlayerCardsChanged += new GamePlayerEventHandler(gameMonitor1_OtherPlayerCardsChanged);
            gameMonitor1.GamePaused += new GameEventHandler(gameMonitor1_GamePaused);
            gameMonitor1.GameResumed += new GameEventHandler(gameMonitor1_GameResumed);
            gameMonitor1.GameWon += new GameEventHandler(gameMonitor1_GameWon);
            gameMonitor1.GameAborted +=new GameEventHandler(gameMonitor1_GameAborted);

            // Create a Game
            result = gameManager.CreateGame(testSession, deck1, GameType.Well, "Test Game 1", 8, gameMonitor1);
            Assert.IsTrue(result.Success);
            GameInfo game1 = result.Data as GameInfo;
            Assert.IsNotNull(game1);
            Assert.IsNotEqual(Guid.Empty, game1.Id);
            Assert.IsEqual(8, game1.MaxNumberOfPlayers);

            // Get list of available game
            result = gameManager.AvailableGames(testSession);
            Assert.IsTrue(result.Success);
            List<GameInfo> gameList = result.Data as List<GameInfo>;
            Assert.IsNotNull(gameList);
            Assert.IsEqual(1, gameList.Count);
            Assert.IsEqual(game1.Id, gameList[0].Id);
            Assert.IsEqual(game1.Title, gameList[0].Title);

            // Create a Game
            // TODO: Create a new monitor
            result = gameManager.CreateGame(testSession, deck1, GameType.Poison, "Test Game 2", 6, gameMonitor1);
            Assert.IsTrue(result.Success);
            GameInfo game2 = result.Data as GameInfo;
            Assert.IsNotNull(game2);
            Assert.IsNotEqual(Guid.Empty, game2.Id);
            Assert.IsEqual(6, game2.MaxNumberOfPlayers);

            // Get list of available game
            result = gameManager.AvailableGames(testSession);
            Assert.IsTrue(result.Success);
            gameList = result.Data as List<GameInfo>;
            Assert.IsNotNull(gameList);
            Assert.IsEqual(2, gameList.Count);
            Assert.IsEqual(game1.Id, gameList[0].Id);
            Assert.IsEqual(game1.Title, gameList[0].Title);
            Assert.IsEqual(game2.Id, gameList[1].Id);
            Assert.IsEqual(game2.Title, gameList[1].Title);

            // Create another session and monitor (for another player), and have that player join the game
            Session playerSession2 = sessionManager.CreateSession("Player #2");

            GameMonitor gameMonitor2 = new GameMonitor();
            gameMonitor2.GameInitialized += new GameEventHandler(gameMonitor2_GameInitialized);
            gameMonitor2.PlayerJoined += new GameEventHandler(gameMonitor2_PlayerJoined);
            gameMonitor2.GameStarted += new GameEventHandler(gameMonitor2_GameStarted);
            gameMonitor2.MyCardsChanged += new GameEventHandler(gameMonitor2_MyCardsChanged);
            gameMonitor2.OtherPlayerCardsChanged += new GamePlayerEventHandler(gameMonitor2_OtherPlayerCardsChanged);
            gameMonitor2.GamePaused += new GameEventHandler(gameMonitor2_GamePaused);
            gameMonitor2.GameResumed += new GameEventHandler(gameMonitor2_GameResumed);
            gameMonitor2.GameWon += new GameEventHandler(gameMonitor2_GameWon);
            gameMonitor2.GameAborted += new GameEventHandler(gameMonitor2_GameAborted);

            result = gameManager.JoinGame(playerSession2, game1.Id, gameMonitor2);
            Assert.IsTrue(result.Success);

            // Create a third session and monitor (for third player), and have that player join the game
            Session playerSession3 = sessionManager.CreateSession("Player #3");
 
            GameMonitor gameMonitor3 = new GameMonitor();
            gameMonitor3.GameInitialized += new GameEventHandler(gameMonitor3_GameInitialized);
            gameMonitor3.PlayerJoined += new GameEventHandler(gameMonitor3_PlayerJoined);
            gameMonitor3.GameStarted += new GameEventHandler(gameMonitor3_GameStarted);
            gameMonitor3.MyCardsChanged += new GameEventHandler(gameMonitor3_MyCardsChanged);
            gameMonitor3.OtherPlayerCardsChanged += new GamePlayerEventHandler(gameMonitor3_OtherPlayerCardsChanged);
            gameMonitor3.GamePaused += new GameEventHandler(gameMonitor3_GamePaused);
            gameMonitor3.GameResumed += new GameEventHandler(gameMonitor3_GameResumed);
            gameMonitor3.GameWon += new GameEventHandler(gameMonitor3_GameWon);
            gameMonitor3.GameAborted += new GameEventHandler(gameMonitor3_GameAborted);
            
            result = gameManager.JoinGame(playerSession3, game1.Id, gameMonitor3);
            Assert.IsTrue(result.Success);

            // Start the game
            result = gameManager.StartGame(testSession, game1.Id);
            Assert.IsTrue(result.Success);



        }

        #region Event Handlers for Player 1
        void gameMonitor1_GameInitialized(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified of Game Initialization");
        }

        void gameMonitor1_PlayerJoined(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Another Player Joined");
        }

        void gameMonitor1_GameStarted(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Game Started");
        }

        void gameMonitor1_MyCardsChanged(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Own Cards Changed");
        }

        void gameMonitor1_OtherPlayerCardsChanged(GameMonitor monitor, int playerIndex)
        {
            Console.WriteLine("Player 1 Notified that Other Player Cards Changed");
        }

        void gameMonitor1_GamePaused(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Game Paused");
        }

        void gameMonitor1_GameResumed(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Game Resumed");
        }

        void gameMonitor1_GameWon(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Game Won");
        }

        void gameMonitor1_GameAborted(GameMonitor monitor)
        {
            Console.WriteLine("Player 1 Notified that Game Aborted");
        }
        #endregion

        #region Event Handlers for Player 2
        void gameMonitor2_GameInitialized(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified of Game Initialization");
        }

        void gameMonitor2_PlayerJoined(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Another Player Joined");
        }

        void gameMonitor2_GameStarted(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Game Started");
        }

        void gameMonitor2_MyCardsChanged(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Own Cards Changed");
        }

        void gameMonitor2_OtherPlayerCardsChanged(GameMonitor monitor, int playerIndex)
        {
            Console.WriteLine("Player 2 Notified that Other Player Cards Changed");
        }

        void gameMonitor2_GamePaused(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Game Paused");
        }

        void gameMonitor2_GameResumed(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Game Resumed");
        }

        void gameMonitor2_GameWon(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Game Won");
        }

        void gameMonitor2_GameAborted(GameMonitor monitor)
        {
            Console.WriteLine("Player 2 Notified that Game Aborted");
        }
        #endregion

        #region Event Handlers for Player #3
        void gameMonitor3_GameInitialized(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified of Game Initialization");
        }

        void gameMonitor3_PlayerJoined(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Another Player Joined");
        }

        void gameMonitor3_GameStarted(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Game Started");
        }

        void gameMonitor3_MyCardsChanged(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Own Cards Changed");
        }

        void gameMonitor3_OtherPlayerCardsChanged(GameMonitor monitor, int playerIndex)
        {
            Console.WriteLine("Player 3 Notified that Other Player Cards Changed");
        }

        void gameMonitor3_GamePaused(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Game Paused");
        }

        void gameMonitor3_GameResumed(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Game Resumed");
        }

        void gameMonitor3_GameWon(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Game Won");
        }

        void gameMonitor3_GameAborted(GameMonitor monitor)
        {
            Console.WriteLine("Player 3 Notified that Game Aborted");
        }
        #endregion
#endif
        #endregion
    }
}
